package gclib.entities;

import gclib.Abilities;
import gclib.Character;
import gclib.Classes.TYPE;
import gclib.inventory.Weapon;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * A Power is a way of acting out in a character of Dungeons and Dragons.
 * Various classes have their own naming for Powers. Clerics call them Prayers, Fighters call them Exploits, Warlocks call them Spells, and so on and so forth.
 * There are many aspects to a Power as they define most types of interaction between characters in the world. Either through attacks, utilization, or survivability.
 * <br/>
 * Here is an example of a Power used by a Cleric from Player's Handbook page 62.
 * <div>
 *	<h3>Healing Word</h3>
 *	<div>
 *   <table width="100%" style="background-color:yellow;">
 *    <tr><td width="80%">Keywords: Divine, Healing</td><td>Ref: PHB.62</td></tr>
 *   </table>
 *   <table width="100%" style="background-color:green;">
 *    <tr>
 *     <td width="20%"><div>Minor</div><b>ACTION</b></td>
 *     <td width="20%">
 *      <table width="100%" style="background-color:yellow;">
 *       <tr><td>Melee Range</td><td>Ranged Range</td></tr>
 *       <tr><td>Blast Range</td><td>Burst Range</td></tr>
 *      </table>
 *     </td>
 *	   <td><div>Close Burst 5 (10 at 11th level, 15 at 21st level)</div><b>Range</b></td>
 *    </tr>
 *   </table>
 *   <table width="100%" style="background-color:yellow;">
 *    <tr>
 *     <td width="40%">
 *      <table width="100%" style="background-color:green;">
 *       <tr><td>&nbsp;</td><td rowspan="2"><b>VS</b></td><td>&nbsp;</td></tr>
 *       <tr><td>Attack</td><td>Defense</td></tr>
 *      </table>
 *     </td>
 *	   <td><div>You or one ally</div><b>Target</b></td>
 *    </tr>
 *   </table>
 *   <p>
 *    <i>Special</i>: You can use this power twice per encounter, but only once per round. At 16th level, you can use this power three times per encounter.
 *   </p>
 *   <p>
 *    <i>Effect</i>: The target can spend a healing surge and regain an additional 1d6 hit points.<br>Increase the amount of additional hit points regained to
 * 2d6 at 6th level, 3d6 at 11th level, 4d6 at 16th level, 5d6 at 21st level, and 6d6 at 26th level.
 *   </p>
 *	</div>
 * </div>
 * @author Jason
 */
public class Power {

    /** */
    private String flavorText;
    /** */
    protected gclib.Character character;
    /** From the example depicted in the class javadoc, the value would be AT_WILL */
    private USE_TYPE useType;
    /** From the example depicted in the class javadoc, the value would be MINOR */
    private ACTION_TYPE actionType;
    /** From the example depicted in the class javadoc, the value would be "Healing Word" */
    private String name;
    /** From the example depicted in the class javadoc, the value would be an object representing PHB pg 62 */
    private Object reference;//physical reference to this power
    /** From the example depicted in the class javadoc, the value would be nothing as this is a utility type power and you or your ally would refuse this. */
    private Object attack;
    /** From the example depicted in the class javadoc, the value would be nothing as this is a utility type power and you or your ally would/should refuse this. */
    private Object defense;
    /** */
    private String effectText;
    /** If this is a class feature, this is the class it belongs  */
    private gclib.Classes.TYPE forClass;
    /** Origination of the Power, usually the character. */
    private Object source;
    /** */
    private String specialText;
    /** Destination or target. */
    private Object target;
    /** */
    private String targetText;
    /** Level of the power, in the case of the example this is a class feature and therefore has no level */
    private Integer level;
    /** */
    private String damage;
    /** Number of usages per encounter as some ENCOUNTER USE_TYPE Powers have a special to use more than once per encounter. The example in the class javadoc specifies a usage of twice per encounter but only once per round. */
    private int usedPerEncounter;
    /** Similar to usedPerEncounter, this allows for DAILY USE_TYPE Powers to have a similar special. */
    private int usedPerDay;
    /** Flag used to determine if the Power is usable */
    private boolean used;
    /** Some Powers can be readied. (Need to do more reading on this.) */
    private boolean readied;
    /** Keywords that signify various attributes of using this Power */
    private Set<String> keywords;
    /** <b>Currently Under Development</b><br/>As the value of this is simply not an attribute, it must be parsed/decrypted to make sense of the text entry in an object oriented world. */
    private Set<Effect> onHitEffects;
    private String hitText;
    /** <b>Currently Under Development</b><br/>As the value of this is simply not an attribute, it must be parsed/decrypted to make sense of the text entry in an object oriented world. */
    private Set<Effect> onMissEffects;
    private String missText;
    /** <b>Currently Under Development</b><br/>As the value of this is simply not an attribute, it must be parsed/decrypted to make sense of the text entry in an object oriented world. */
    private Set<Effect> automaticEffects;
    private String whiteText;
    /** <b>Currently Under Development</b><br/>As the value of this is simply not an attribute, it must be parsed/decrypted to make sense of the text entry in an object oriented world. */
    private Set<Object> triggers;
    /** <b>Currently Under Development</b><br/>As the value of this is simply not an attribute, it must be parsed/decrypted to make sense of the text entry in an object oriented world. */
    private Set<Object> requirements;
    /** <b>Currently Under Development</b><br/>As the value of this is simply not an attribute, it must be parsed/decrypted to make sense of the text entry in an object oriented world. Furthermore this 'value' can change depending on the level as depicted by the example in the class javadoc. */
    private Set<Range> range;
    private AttackType attackType;

    public Power() {
    }

    public String getWhiteText() {
        return whiteText;
    }

    public void setWhiteText(String whiteText) {
        this.whiteText = whiteText;
    }

    public String getMissText() {
        return missText;
    }

    public void setMissText(String missText) {
        this.missText = missText;
    }

    public String getHitText() {
        return hitText;
    }

    public void setHitText(String hitText) {
        this.hitText = hitText;
    }

    public String getTargetText() {
        return targetText;
    }

    public void setTargetText(String targetText) {
        this.targetText = targetText;
    }

    public String getFlavorText() {
        return flavorText;
    }

    public void setFlavorText(String flavorText) {
        this.flavorText = flavorText;
    }

    public AttackType getAttackType() {
        return attackType;
    }

    public void setAttackType(AttackType attackType) {
        this.attackType = attackType;
    }

    public ACTION_TYPE getActionType() {
        return actionType;
    }

    public void setActionType(ACTION_TYPE actionType) {
        this.actionType = actionType;
    }

    public Object getAttack() {
        return attack;
    }

    public void setAttack(Object attack) {
        this.attack = attack;
    }

    public Set<Effect> getAutomaticEffects() {
        if(automaticEffects == null)
            automaticEffects = new HashSet<Effect>();
        return automaticEffects;
    }

    public void setAutomaticEffects(Set<Effect> automaticEffects) {
        this.automaticEffects = automaticEffects;
    }

    public Object getReference() {
        return reference;
    }

    public void setReference(String book, Integer page) {
        //create a reference
    }

    public void setReference(Object reference) {
        this.reference = reference;
    }

    public TYPE getForClass() {
        return forClass;
    }

    public void setForClass(TYPE forClass) {
        this.forClass = forClass;
    }

    public Object getDefense() {
        return defense;
    }

    public void setDefense(Object defense) {
        this.defense = defense;
    }

    public String getEffectText() {
        return effectText;
    }

    public void setEffectText(String effectText) {
        this.effectText = effectText;
    }

    public Set<String> getKeywords() {
        return keywords;
    }

    public void setKeywords(Set<String> keywords) {
        this.keywords = keywords;
    }

    public Integer getLevel() {
        return level;
    }

    public void setLevel(Integer level) {
        this.level = level;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Effect> getOnHitEffects() {
        return onHitEffects;
    }

    public void setOnHitEffects(Set<Effect> onHitEffects) {
        this.onHitEffects = onHitEffects;
    }

    public Set<Effect> getOnMissEffects() {
        return onMissEffects;
    }

    public void setOnMissEffects(Set<Effect> onMissEffects) {
        this.onMissEffects = onMissEffects;
    }

    public Set<Range> getRange() {
        return range;
    }

    public void setRange(Set<Range> range) {
        this.range = range;
    }

    public boolean isReadied() {
        return readied;
    }

    public void setReadied(boolean readied) {
        this.readied = readied;
    }

    public Set<Object> getRequirements() {
        return requirements;
    }

    public void setRequirements(Set<Object> requirements) {
        this.requirements = requirements;
    }

    public Object getSource() {
        return source;
    }

    public void setSource(Object source) {
        this.source = source;
    }

    public String getSpecialText() {
        return specialText;
    }

    public void setSpecialText(String specialText) {
        this.specialText = specialText;
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Set<Object> getTriggers() {
        return triggers;
    }

    public void setTriggers(Set<Object> triggers) {
        this.triggers = triggers;
    }

    public USE_TYPE getUseType() {
        return useType;
    }

    public void setUseType(USE_TYPE useType) {
        this.useType = useType;
    }

    public boolean isUsed() {
        return used;
    }

    public void setUsed(boolean used) {
        this.used = used;
    }

    public int getUsedPerDay() {
        return usedPerDay;
    }

    public void setUsedPerDay(int usedPerDay) {
        this.usedPerDay = usedPerDay;
    }

    public int getUsedPerEncounter() {
        return usedPerEncounter;
    }

    public void setUsedPerEncounter(int usedPerEncounter) {
        this.usedPerEncounter = usedPerEncounter;
    }

    public String getDamage() {
        return damage;
    }

    public void setDamage(String damage) {
        this.damage = damage;
    }

    public Character getCharacter() {
        return character;
    }

    public void setCharacter(Character character) {
        this.character = character;
    }

    public void parseDamage(String dmgString) {

        StringBuilder sb = new StringBuilder(dmgString);
        int from = dmgString.indexOf(" damage", 0);
        sb.delete(from, sb.length());
        String[] vals = sb.toString().split("( \\+ )");//i should now have {"2[W]", "1d8", "Strength modifier", "Wisdom modifier radiant"}
        //after somehow querying for modifiers
        for (int valIndex = 0; valIndex < vals.length; valIndex++) {
            String val = vals[valIndex];
            if (Pattern.matches("\\A(\\D)* modifier( (\\D)+)?\\z", val)) {
                String sub = val.substring(0, 3);
                int value = 0;
                if (sub.equalsIgnoreCase(Abilities.TYPE.STR.name())) {
                    value = character.getAbilities().getModifier(Abilities.TYPE.STR);
                } else if (sub.equalsIgnoreCase(Abilities.TYPE.CON.name())) {
                    value = character.getAbilities().getModifier(Abilities.TYPE.CON);
                } else if (sub.equalsIgnoreCase(Abilities.TYPE.DEX.name())) {
                    value = character.getAbilities().getModifier(Abilities.TYPE.DEX);
                } else if (sub.equalsIgnoreCase(Abilities.TYPE.INT.name())) {
                    value = character.getAbilities().getModifier(Abilities.TYPE.INT);
                } else if (sub.equalsIgnoreCase(Abilities.TYPE.WIS.name())) {
                    value = character.getAbilities().getModifier(Abilities.TYPE.WIS);
                } else if (sub.equalsIgnoreCase(Abilities.TYPE.CHA.name())) {
                    value = character.getAbilities().getModifier(Abilities.TYPE.CHA);
                }
                vals[valIndex] = val.replaceFirst("\\A(\\D)* modifier", value + "");
            } else if (Pattern.matches("\\A(\\d)*\\[W\\]\\z", val)) {
                int weaponCount = Integer.parseInt(val.replaceAll("\\[W\\]", ""));//if 2[W] weaponCount = 2
                Weapon w = character.getEquipment().getEquiptWeapon();
                int a = weaponCount * w.getDieCount();
                String result = a + "d" + w.getDieSize() + w.getDamageType();
                vals[valIndex] = result;
            }
        }
        // need to expand the array by one if there is a weapon enhancement
        if (character.getEquipment().getEquiptWeapon().getEnhancement() > 0) {
            vals = Arrays.copyOf(vals, vals.length + 1);
            vals[vals.length - 1] = character.getEquipment().getEquiptWeapon().getEnhancement() + " " + character.getEquipment().getEquiptWeapon().getEnhancementDmgType();
        }

        //then run it through a simplifier
        simplifyDmg(vals);
        sortDmg(vals);
        setDamage(buildString(vals));

    }

    protected void simplifyDmg(String[] values) {
        java.util.regex.Pattern p1 = Pattern.compile("\\A(\\d)+[Dd](\\d)+\\z");
        java.util.regex.Pattern p2 = Pattern.compile("\\A(\\d)+[Dd](\\d)+ (\\D)+");
        java.util.regex.Pattern p3 = Pattern.compile("\\A(\\d)+\\z");
        java.util.regex.Pattern p4 = Pattern.compile("\\A(\\d)+ (\\D)+");
        Stack<String> aList = new Stack<String>();
        for (String value : values) {
            aList.add(value);
        }
        Set<String> vals = new HashSet<String>();
        java.util.regex.Matcher m;
        while (!aList.empty()) {
            String thePeek = aList.peek();
            if (p1.matcher(thePeek).find()) {
                String dieCount = aList.peek().substring(0, aList.peek().indexOf('d'));//if following the example i should now have 4 from 4d6 or 2 from 2d12
                int dieCountInt = Integer.parseInt(dieCount);
                String dieSize = aList.peek().substring(aList.peek().indexOf('d') + 1, aList.peek().length());//if following the example i should now have the 6 from 4d6 or 12 from 2d12
                aList.pop();
                int elementCount = aList.size();
                for (int i = 0; i < elementCount; i++) {
                    String element = aList.pop();
                    if (Pattern.compile("\\A(\\d)+[Dd]" + dieSize + "\\z").matcher(element).find()) {
                        String dieCount2 = element.substring(0, element.indexOf('d'));//if following the example i should now have 4 from 4d6 or 2 from 2d12
                        dieCountInt += Integer.parseInt(dieCount2);
                    } else {//put the element back on the stack
                        aList.add(0, element);
                    }
                }
                vals.add(dieCountInt + "d" + dieSize);
                continue;
            }
            if (p2.matcher(thePeek).find()) {
                String dieCount = aList.peek().substring(0, aList.peek().indexOf('d'));//if following the example i should now have 4 from 4d6 radiant or 2 from 2d12 holy
                int dieCountInt = Integer.parseInt(dieCount);
                String dieSize = aList.peek().substring(aList.peek().indexOf('d') + 1, aList.peek().indexOf(' '));//if following the example i should now have the 6 from 4d6 radiant or 12 from 2d12 holy
                String dmgType = aList.peek().substring(aList.peek().indexOf(' ') + 1);//if following the example i should now have radiant from 4d6 radiant or holy from 2d12 holy
                aList.pop();
                int elementCount = aList.size();
                for (int i = 0; i < elementCount; i++) {
                    String element = aList.pop();
                    if (Pattern.compile("\\A(\\d)+[Dd]" + dieSize + " " + dmgType + "\\z").matcher(element).find()) {
                        String dieCount2 = element.substring(0, element.indexOf('d'));//if following the example i should now have 4 from 4d6 or 2 from 2d12
                        dieCountInt += Integer.parseInt(dieCount2);
                    } else {//put the element back on the stack
                        aList.add(0, element);
                    }
                }
                vals.add(dieCountInt + "d" + dieSize + " " + dmgType);
                continue;
            }
            if (p3.matcher(thePeek).find()) {
                String bonus = aList.peek();//should be a simple parse
                int bonusInt = Integer.parseInt(bonus);
                aList.pop();
                int elementCount = aList.size();
                for (int i = 0; i < elementCount; i++) {
                    String element = aList.pop();
                    if (Pattern.compile("\\A(\\d)+\\z").matcher(element).find()) {
                        bonusInt += Integer.parseInt(element);
                    } else {//put the element back on the stack
                        aList.add(0, element);
                    }
                }
                vals.add(bonusInt + "");
                continue;
            }
            if (p4.matcher(thePeek).find()) {
                String bonus = aList.peek().substring(0, aList.peek().indexOf(' '));//if following the example i should now have 4 from 4 radiant or 2 from 2 holy
                int bonusInt = Integer.parseInt(bonus);
                String dmgType = aList.peek().substring(aList.peek().indexOf(' ') + 1);//if following the example i should now have radiant from 4 radiant or holy from 2 holy
                aList.pop();
                int elementCount = aList.size();
                for (int i = 0; i < elementCount; i++) {
                    String element = aList.pop();
                    if (Pattern.compile("\\A(\\d)+ " + dmgType + "\\z").matcher(element).find()) {
                        String bonus2 = element.substring(0, element.indexOf(' '));//if following the example i should now have 4 from 4 radiant or 2 from 2 holy
                        bonusInt += Integer.parseInt(bonus2);
                    } else {//put the element back on the stack
                        aList.add(0, element);
                    }
                }
                vals.add(bonusInt + " " + dmgType);
                continue;
            }
        }
        //if format is xdy
        //if format is xdy string
        //if format is n
        //if format is n string
        values = vals.toArray(values);
//        StringBuffer sb = new StringBuffer(0);
//        for (String s : vals) {
//            sb.append(s).append(" + ");
//        }
//        sb.delete(sb.toString().length() - 2, sb.toString().length());
//        return sb.toString();
    }

    protected String buildString(String[] vals) {

        StringBuilder sb = new StringBuilder(0);
        for (String s : vals) {
            sb.append(s).append(" + ");
        }
        sb.delete(sb.toString().length() - 3, sb.toString().length());
        return sb.toString();
    }

    protected void sortDmg(String[] values) {
        List<String> vals = Arrays.asList(values);
        Collections.sort(vals, Collections.reverseOrder(new Comparator<String>() {
            java.util.regex.Pattern p1 = Pattern.compile("\\A(\\d)+[Dd](\\d)+\\z");
            java.util.regex.Pattern p2 = Pattern.compile("\\A(\\d)+[Dd](\\d)+ (\\D)+");
            java.util.regex.Pattern p3 = Pattern.compile("\\A(\\d)+\\z");
            java.util.regex.Pattern p4 = Pattern.compile("\\A(\\d)+ (\\D)+");

            @Override
            public int compare(String o1, String o2) {
                Integer typeA = getType(o1);
                Integer typeB = getType(o2);
                
                
                if(typeA==typeB){
                    return o1.compareTo(o2);
                }
                return typeA.compareTo(typeB);
            }
            
            private Integer getType(String txt) {
                if(p1.matcher(txt).matches())
                    return 4;
                if(p2.matcher(txt).matches())
                    return 3;
                if(p3.matcher(txt).matches())
                    return 2;
                if(p4.matcher(txt).matches())
                    return 1;
                return 0;
            }
        }));
    }

    public enum USE_TYPE {

        AT_WILL, ENCOUNTER, DAILY
    };

    public enum ACTION_TYPE {

        MINOR, STANDARD, FREE, MOVE, IMMEDIATE_INTERRUPT, IMMEDIATE_REACTION
    };
}
